﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Security.Cryptography;
using Informant.UI.Properties;
using System.Text;
using System.Linq;

namespace Informant.UI.Credentials
{
	public class CredentialsHelper
	{
		#region Properties
		private static IsolatedStorageFile _isolatedStore;
		private static IsolatedStorageFile IsolatedStore
		{
			get
			{
				if (_isolatedStore == null)
					_isolatedStore = IsolatedStorageFile.GetUserStoreForAssembly();
				return _isolatedStore;
			}
			set
			{
				if (value == _isolatedStore)
					return;

				if (value == null && _isolatedStore != null)
				{
					_isolatedStore.Close();
				}

				_isolatedStore = null;
			}
		}

		private static string _credentialsFileDecryptionKey;
		public static string CredentialsFileDecryptionKey
		{
			get
			{
				if (String.IsNullOrEmpty(_credentialsFileDecryptionKey))
					_credentialsFileDecryptionKey = LoadCredentialsDecryptionKey();
				return _credentialsFileDecryptionKey;
			}
		}
		#endregion

		#region Public Methods
		public static IsoCreds LoadCredsFromStorage()
		{
			IsoCreds creds = null;
			try
			{
				if (ExistsInIsolatedStorage(Resources.CredentialsFilename))
				{
					CryptoStream decryptedStream = GetDecryptedStream(Resources.CredentialsFilename, CredentialsFileDecryptionKey);
					List<String> lines = new List<String>();
					using (StreamReader reader = new StreamReader(decryptedStream))
					{
						while (!reader.EndOfStream)
						{
							lines.Add(reader.ReadLine());
						}
					}

					if (lines.Count == 2)
					{
						creds = new IsoCreds
						        	{
						        		Username = lines[0],
						        		Password = lines[1]
						        	};
					}
				}
			}
			catch (Exception e)
			{
				Debug.WriteLine(e.Message);
			}

			IsolatedStore = null;
			return creds;
		}

		public static Boolean SaveCredsToStorage(IsoCreds newCreds)
		{
			try
			{
				string credsToEncrypt = newCreds.Username + Environment.NewLine + newCreds.Password;
				EncryptAndSaveStream(Resources.CredentialsFilename, CredentialsFileDecryptionKey, credsToEncrypt);
				return true;
			}
			catch (Exception e)
			{
				Debug.WriteLine(e.Message);
			}
			return false;
		}

		public static void RemoveAnySavedCredsFromStorage()
		{
			if (ExistsInIsolatedStorage(Resources.CredentialsFilename))
			{
				IsolatedStore.DeleteFile(Resources.CredentialsFilename);
			}
		}
		#endregion

		#region Private Implementation
		private static String LoadCredentialsDecryptionKey()
		{
			string decryptionKey;
			if (!ExistsInIsolatedStorage(Resources.FileEncryptionKeyFileName))
			{
				//Generate Key and save it to file
				DES desCrypto = DES.Create();

				decryptionKey = Encoding.ASCII.GetString(desCrypto.Key);

				EncryptAndSaveStream(Resources.FileEncryptionKeyFileName, Resources.InitialEncryptionKeyForFileEncryptionKey,
				                     decryptionKey);
			}
			else
			{
				CryptoStream decryptedFileStream = GetDecryptedStream(Resources.FileEncryptionKeyFileName,
				                                                      Resources.InitialEncryptionKeyForFileEncryptionKey);

				using (StreamReader reader = new StreamReader(decryptedFileStream))
				{
					decryptionKey = reader.ReadToEnd();
				}
			}

			return decryptionKey;
		}

		private static Boolean ExistsInIsolatedStorage(String fileName)
		{
			return IsolatedStore.GetFileNames(fileName).Count() == 1;
		}

		private static CryptoStream GetDecryptedStream(String filename, String decryptionKey)
		{
			ICryptoTransform desDecryptor = GetDESCryptoProvider(decryptionKey).CreateDecryptor();
			return new CryptoStream(GetIsolatedStorageCredentialsFileStream(filename, true), desDecryptor,
			                        CryptoStreamMode.Read);
		}

		private static void EncryptAndSaveStream(String fileName, String encryptionKey, String dataToEncrypt)
		{
			Byte[] bytesToEncrypt = Encoding.ASCII.GetBytes(dataToEncrypt);
			ICryptoTransform desEncryptor = GetDESCryptoProvider(encryptionKey).CreateEncryptor();
			CryptoStream encryptedStream =
				new CryptoStream(GetIsolatedStorageCredentialsFileStream(fileName, false), desEncryptor,
				                 CryptoStreamMode.Write);

			encryptedStream.Write(bytesToEncrypt, 0, bytesToEncrypt.Length);
			encryptedStream.Close();
		}

		private static DESCryptoServiceProvider GetDESCryptoProvider(string decryptionKey)
		{
			DESCryptoServiceProvider desCryptoProvider = new DESCryptoServiceProvider();
			Byte[] keyBytes = Encoding.ASCII.GetBytes(decryptionKey);
			desCryptoProvider.Key = keyBytes;
			desCryptoProvider.IV = keyBytes;
			return desCryptoProvider;
		}

		private static IsolatedStorageFileStream GetIsolatedStorageCredentialsFileStream(String filename, Boolean loading)
		{
			return loading
			       	? new IsolatedStorageFileStream(filename, FileMode.Open, IsolatedStore)
			       	: new IsolatedStorageFileStream(filename, FileMode.Create, IsolatedStore);
		}
		#endregion
	}
}